﻿#include <sys/stat.h>
#ifdef WIN32
#include <windows.h>
#include <Shlwapi.h>
#include <io.h>
#else
#include <dirent.h>
#endif
#include <functional>
#include <map>
#include <algorithm>

#include "SLT_filesystem.h"
#include "SLT_logImpl.h"

CLogHelper::CLogHelper()
{
    char buf[256] = { 0 };
    SLT_getProcessName(buf, sizeof(buf));
    m_logPrefixName = buf;
}

CLogHelper::~CLogHelper()
{
    if (m_fp) {
        fclose(m_fp);
    }
}

std::string CLogHelper::openAndRead(const char* path)
{
    FILE* _file = fopen(path, "rb");
    std::string content;
    if (!_file)
    {
        //打开失败返回为空
        return content;
    }
    char buf[BUFSIZ];
    size_t ret = 0;
    do
    {
        ret = fread(buf, sizeof(char), BUFSIZ, _file);
        content.append(buf, ret);
    } while (ret == BUFSIZ);
    fclose(_file);
    return content;
}
static std::pair<std::string, std::string> splitPairString(const std::string& str, const std::string& delimiter)
{
    std::string::size_type pos = str.find(delimiter.c_str());
    if (pos == std::string::npos)
    {
        return std::make_pair(str, "");
    }
    return std::make_pair(str.substr(0, pos), str.substr(pos + delimiter.length()));
}
static void trimLogConfig(std::string& str, std::string extIgnore = std::string());
static void trimLogConfig(std::string& str, std::string extIgnore)
{
    if (str.empty()) { return; }
    extIgnore += "\r\n\t ";
    int length = (int)str.length();
    int posBegin = 0;
    int posEnd = 0;

    //trim utf8 file bom
    if (str.length() >= 3
        && (unsigned char)str[0] == 0xef
        && (unsigned char)str[1] == 0xbb
        && (unsigned char)str[2] == 0xbf)
    {
        posBegin = 3;
    }

    //trim character 
    for (int i = posBegin; i < length; i++)
    {
        bool bCheck = false;
        for (int j = 0; j < (int)extIgnore.length(); j++)
        {
            if (str[i] == extIgnore[j])
            {
                bCheck = true;
            }
        }
        if (bCheck)
        {
            if (i == posBegin)
            {
                posBegin++;
            }
        }
        else
        {
            posEnd = i + 1;
        }
    }
    if (posBegin < posEnd)
    {
        str = str.substr(posBegin, posEnd - posBegin);
    }
    else
    {
        str.clear();
    }
}
static bool parseConfigLine(const std::string& line, int curLineNum, std::string& key, std::map<std::string, LoggerInfo>& outInfo)
{
    std::pair<std::string, std::string> kv = splitPairString(line, "=");
    if (kv.first.empty())
    {
        return false;
    }

    trimLogConfig(kv.first);
    trimLogConfig(kv.second);
    if (kv.first.empty() || kv.first.at(0) == '#')
    {
        return true;
    }

    if (kv.first.at(0) == '[')
    {
        trimLogConfig(kv.first, "[]");
        std::transform(kv.first.begin(), kv.first.end(), kv.first.begin(), ::tolower);
        key = kv.first;
        LoggerInfo li;
        li.clear();
        li._key = key;
        outInfo.insert(std::make_pair(li._key, li));
        return true;
    }
    trimLogConfig(kv.first);
    trimLogConfig(kv.second);
    std::map<std::string, LoggerInfo>::iterator iter = outInfo.find(key);
    if (iter == outInfo.end())
    {
        printf("log4z configure warning: not found current logger name:[%s] at line:%d, key=%s, value=%s \r\n",
            key.c_str(), curLineNum, kv.first.c_str(), kv.second.c_str());
        return true;
    }
    std::transform(kv.first.begin(), kv.first.end(), kv.first.begin(), ::tolower);
    //! path
    if (kv.first == "path")
    {
        iter->second._path = kv.second;
        return true;
    }
    else if (kv.first == "logname")
    {
        iter->second._logname = kv.second;
        return true;
    }
    else if (kv.first == "filesize")
    {
        iter->second._filesize = atoi(kv.second.c_str());
        return true;
    }
    else if (kv.first == "filecount")
    {
        iter->second._filecount = atoi(kv.second.c_str());
        return true;
    }
    std::transform(kv.second.begin(), kv.second.end(), kv.second.begin(), ::tolower);
    //! level
    if (kv.first == "level")
    {
        if (kv.second == "trace" || kv.second == "all")
        {
            iter->second._level = SLT_TRACE;
        }
        else if (kv.second == "debug")
        {
            iter->second._level = SLT_DEBUG;
        }
        else if (kv.second == "info")
        {
            iter->second._level = SLT_INFO;
        }
        else if (kv.second == "warn" || kv.second == "warning")
        {
            iter->second._level = SLT_WARM;
        }
        else if (kv.second == "error")
        {
            iter->second._level = SLT_ERROR;
        }
        else if (kv.second == "fatal")
        {
            iter->second._level = SLT_FATAL;
        }
        else if (kv.second == "max")
        {
            iter->second._level = SLT_MAX;
        }

    }
    //! display
    else if (kv.first == "display")
    {
        if (kv.second == "false" || kv.second == "0")
        {
            iter->second._display = 0;
        }
        else
        {
            iter->second._display = 1;
        }
    }
    else if (kv.first == "syncdisplay")
    {
        if (kv.second == "false" || kv.second == "0")
        {
            iter->second._sync_display_out = 0;
        }
        else
        {
            iter->second._sync_display_out = 1;
        }
    }
    return true;
}
static bool parseConfigFromString(std::string content, std::map<std::string, LoggerInfo>& outInfo)
{

    std::string key;
    int curLine = 1;
    std::string line;
    std::string::size_type curPos = 0;
    if (content.empty())
    {
        return true;
    }
    do
    {
        std::string::size_type pos = std::string::npos;
        for (std::string::size_type i = curPos; i < content.length(); ++i)
        {
            //support linux/unix/windows LRCF
            if (content[i] == '\r' || content[i] == '\n')
            {
                pos = i;
                break;
            }
        }
        line = content.substr(curPos, pos - curPos);
        parseConfigLine(line, curLine, key, outInfo);
        curLine++;

        if (pos == std::string::npos)
        {
            break;
        }
        else
        {
            curPos = pos + 1;
        }
    } while (1);
    return true;
}
static void fixPath(std::string& path)
{
    if (path.empty()) { return; }
    for (std::string::iterator iter = path.begin(); iter != path.end(); ++iter)
    {
        if (*iter == '\\') { *iter = '/'; }
    }
    if (path.at(path.length() - 1) != '/') { path.append("/"); }
}
bool CLogHelper::configFromStringImpl(std::string content, bool isUpdate)
{
    std::map<std::string, LoggerInfo> loggerMap;
    if (!parseConfigFromString(content, loggerMap))
    {
        printf("[basetool_log] load config content error \r\n");
        return false;
    }
    for (std::map<std::string, LoggerInfo>::iterator iter = loggerMap.begin(); iter != loggerMap.end(); ++iter)
    {
        //printf("\n----%s:  ", iter->first.c_str());
        if (iter->second._key == "base")
        {
            if (!isUpdate) {
                
                if (!iter->second._path.empty()) {  setLogPath(iter->second._path.c_str()); }
                if (!iter->second._logname.empty()) { setLogFilePrefix(iter->second._logname.c_str()); }
                if (-1 != iter->second._filesize) { setLogLimitSize(iter->second._filesize); }
                if (-1 != iter->second._filecount) { setLogFileCount(iter->second._filecount); }
            }
        }
        else 
        {
            int id = atoi(iter->second._key.c_str());
            if (iter->second._level != SLT_UNKNOW) {setLogLevel(id, iter->second._level); }
            if(-1 != iter->second._display){ setLogDisplay(id, iter->second._display); }
            if(-1 != iter->second._sync_display_out){ setLogDisplaySync(id, iter->second._sync_display_out); }
        }   
    }
    return true;
}
bool CLogHelper::config(const std::string& configPath,bool isUpdate)
{
    if (configPath.empty()) {
        return false;
    }
    _configFile = configPath;
    std::string content = openAndRead(_configFile.c_str());
    if (content.empty()) {
        printf("[basetool_log] load config file content error. filename=%s  !!! !!! \n", configPath.c_str());
        return false;
    }
    return configFromStringImpl(content.c_str(), isUpdate);
}

bool CLogHelper::isDirectory(std::string path)
{
#ifdef WIN32
    return PathIsDirectoryA(path.c_str()) ? true : false;
#else
    DIR* pdir = opendir(path.c_str());
    if (pdir == NULL)
    {
        return false;
    }
    else
    {
        closedir(pdir);
        pdir = NULL;
        return true;
    }
#endif
}
void CLogHelper::initHistoryLogs(std::string path) {

    m_historyLogs.clear();
    _historyLog_info fileLog;
#ifdef WIN32
    _finddata_t fileinfo;
   
    std::string p(path);
    p.append("*");
    intptr_t hFile = _findfirst(p.c_str(), &fileinfo);
    if (hFile != -1) {
        do{
            if (fileinfo.attrib & _A_SUBDIR) {
                //如果找到的是文件夹
                continue;
            }
            else {
                fileLog.path = path;
                fileLog.fileName = fileinfo.name;
                m_historyLogs.push_back(fileLog);
            }
        } while (_findnext(hFile, &fileinfo) == 0);
        _findclose(hFile);
    }
#else

    struct dirent** entry_list, * entry;
    int retCount = scandir(path.c_str(), &entry_list, 0, alphasort);
    if (retCount < 0)
    {
        printf("[basetool_log] scandir \n");
        return;
    }
    for (int i = 0; i < retCount; i++)
    {
        entry = entry_list[i];
        fileLog.path = path;
        fileLog.fileName = entry->d_name;
        m_historyLogs.push_back(fileLog);
    }

    for (int i = 0; i < retCount; ++i)
    {
        free(entry_list[i]);
    }
    free(entry_list);
#endif // WIN32



}
int CLogHelper::init()
{
    if (m_init) {
        return 0;
    }
    fixPath(m_logPath);

    if (!SLT_createRecursionDir(m_logPath.c_str())) {
        printf("[basetool_log]logPath create error: %s\n",m_logPath.c_str());
        return -1;
    }

    printf("[basetool_log]configPath:%s,logPath:%s\n", _configFile.c_str(), m_logPath.c_str());
    initHistoryLogs(m_logPath);
    m_plogList = &m_logList1;

    m_init = true;
    m_pwriteThread = new std::thread(std::bind(&CLogHelper::procLog, this));

    return 0;
}
void CLogHelper::uninit()
{
    if (! m_init) {
        return;
    }
    m_init = false;

    m_pwriteThread->join();
}

void CLogHelper::writeLog(loggerhead* plogger)
{
    if (! m_init) {
        return;
    }
    m_logListLock.lock();
    m_plogList->push_back(plogger);
    m_logListLock.unlock();
}

bool CLogHelper::IsFileAlready()
{
    if (NULL == m_fp || m_curWriteLen >= m_limitSizeB)
    {
        if (m_fp != NULL)
        {
            fclose(m_fp);
            m_fp = NULL;
        }
        Delete_FileCount(m_filecount);

        struct tm ts;
        time_t tt;
        time(&tt);
#ifdef WIN32
		localtime_s(&ts, &tt);
#else
        localtime_r(&tt, &ts);
#endif
        uint32_t iPid = SLT_getProcessID();

        char filename[1024];
        snprintf(filename, sizeof(filename),
            "%s_%04d%02d%02d%02d%02d%02d_%u_%03u.log",
            m_logPrefixName.c_str(), ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, iPid, m_fileIndex);
#ifdef WIN32
#pragma warning(push)    /* C4996 */
#pragma warning( disable : 4996 )
#endif

        std::string fullPathfilename;
        fullPathfilename = m_logPath + filename;
        m_fp = fopen(fullPathfilename.c_str(), "wb");

        if (m_fp != NULL) {
            //fprintf(m_fp, "%-s\t\t\t%-s\t%-s\t%-30s\t%-s\t%-s\n", "日期时间", "线程", "级别", "源文件", "行号", "内容");
            //fflush(m_fp);
            m_curWriteLen = 0;
            m_fileIndex++;
            if (m_fileIndex > m_filecount) {
                m_fileIndex = 0;
            }
        }
        else {
            printf("[basetool_log]fopen failed. m_FullPathFileName = %s, error : %s\n", fullPathfilename.c_str(), strerror(errno));
            return false;
        }
        _historyLog_info fileLog;
        fileLog.path = m_logPath;
        fileLog.fileName = filename;
        m_historyLogs.push_back(fileLog);


#ifdef WIN32
#pragma warning(pop)    /* C4996 */
#endif       
    }
    return true;
}
void CLogHelper::Delete_FileCount(int count)
{
    std::string logPath;
    while ((int)m_historyLogs.size() > count)
    {
        logPath = m_historyLogs.front().path + m_historyLogs.front().fileName;
        if ((::remove(logPath.c_str())) != 0)
        {
            printf("[basetool_log]remove logfile fail,%s\n", logPath.c_str());
        }
        m_historyLogs.pop_front();
    }
}
void CLogHelper::procLog()
{
    std::deque< loggerhead*>* tempPlogList;
    while (1) {
        while (1) {
            m_logListLock.lock();
            if (m_plogList->empty()) {
                m_logListLock.unlock();
                if (!m_init) {
                    goto procLog_end;
                }
                break;
            }
            tempPlogList = m_plogList;
            if (m_plogList == &m_logList1) {
                m_plogList = &m_logList2;
            }
            else {
                m_plogList = &m_logList1;
            }
            m_logListLock.unlock();

            for (const auto& logItem : *tempPlogList) {
                IsFileAlready();

                if (logItem->_disp) {
                    printf("%s", logItem->_data);
                }

                if (m_fp != NULL) {
                    fwrite(logItem->_data, 1, logItem->_len, m_fp);
                    m_curWriteLen += logItem->_len;
                }
                delete logItem;    //删除buf
            }
            tempPlogList->clear();
            if(m_bconfig){ //动态读取配置文件
                if (m_delFileTimer.elapseMs() > 10000) {
                    m_delFileTimer.start();
                    std::string tempconf = _configFile;
                    config(tempconf, true);
                }
            } 
        }

        if (m_flushTimer.elapseMs() > 500) {
            m_flushTimer.start();
            if (m_fp) {
                fflush(m_fp);
            }
        }
        if(m_bconfig){ //动态读取配置文件
            if (m_delFileTimer.elapseMs() > 5000) {
                m_delFileTimer.start();
                std::string tempconf = _configFile;
                config(tempconf, true);
            }
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
procLog_end:
    const char* ptemplog = "********slt_log_end thread stop********";
    printf("%s\n", ptemplog);
    fwrite(ptemplog, 1, strlen(ptemplog), m_fp);
    fclose(m_fp);
    m_fp = nullptr;
}


void CLogHelper::setLogLevel(int _id, int _level)
{
    m_loggers[_id]._level = _level;
}
void CLogHelper::setLogDisplay(int _id, int _disp)
{
    m_loggers[_id]._display = _disp;
}
void CLogHelper::setLogDisplaySync(int _id, int _sync)
{
    m_loggers[_id]._sync_display_out = _sync;
}




//long int CLogHelper::GetFileSize()
//{
//	fseek(m_fp, SEEK_SET, SEEK_END); //size must <= 2G
//	return ftell(m_fp);
//}
//unsigned long long CLogHelper::GetFileSize(const char* filename)
//{
//	struct stat buf;
//	if (stat(filename, &buf) < 0)
//	{
//		return 0;
//	}
//	return (unsigned long long)buf.st_size;
//}


//void CLogHelper::Delete_File(char* cleandir, int days)
//{
//#ifndef WIN32
//    if(cleandir == NULL)
//    {
//        return;
//    }
//
//    DIR *dir;
//    struct dirent *de;
//    struct stat buf;
//    time_t tim;
//    int i;
//    dir = opendir(cleandir);
//    if(dir==NULL)
//    {
//        printf("open directory %s error:%s/n",cleandir,strerror(errno));
//        return;
//    }
//    chdir(cleandir);
//    while((de=readdir(dir))!=NULL)
//    {
//        bzero(&buf,sizeof(buf));
//        i=lstat(de->d_name,&buf);
//        if(i<0)
//        {
//            printf("i=%d,de->d_name=%s/n",i,de->d_name);
//            break;
//        }
//        else
//        {
//            if(S_ISDIR(buf.st_mode))
//            {
//                if((strcmp(de->d_name,".")==0)||(strcmp(de->d_name,".."))==0)
//                    continue;
//                Delete_File(de->d_name,days);
//            }
//            else
//            {
//                time(&tim);
//                if (tim - days * 24 * 60 * 60 > buf.st_mtime)
//                {
//                    printf("delete file:%s/n", de->d_name);
//                    unlink(de->d_name);//delete file
//                }
//            }
//        }
//    }
//    closedir(dir);
//#endif
//}

//int CLogHelper::CustomFilter(const struct dirent* pDir)
//{
//#ifndef WIN32
//	if (0 != strncmp(pDir->d_name, ".", 1) && 0 != strncmp(pDir->d_name, "..", 2))
//	{
//		return 1;
//	}
//#endif
//	return 0;
//}